﻿using UnityEngine;
using System;
using System.Collections;
using jp.nyatla.nyartoolkit.cs.markersystem;
using jp.nyatla.nyartoolkit.cs.core;
using NyARUnityUtils;
using System.IO;

namespace edu.wpi.cs.Project1
{
    /// <summary>
    /// The possible game phases.
    /// </summary>
    public enum GameState
    {
        Start,
        Adjusting,
        Playing,
        GameOver
    }

    /// <summary>
    /// The AR physics ball game behaviour.
    /// This is the main script of the application, which is attached to the main camera.
    /// </summary>
    public class ARPhysicsGameBehaviour : MonoBehaviour
    {
        /// <summary>
        /// The AR marker system.
        /// </summary>
        private NyARUnityMarkerSystem markerSystem;

        /// <summary>
        /// The web cam sensor system.
        /// </summary>
        private NyARUnityWebCam webCamSensor;

        /// <summary>
        /// The background panels marker ID.
        /// </summary>
        private GameObject backgroundPanel;

        /// <summary>
        /// The emitter which emits balls.
        /// </summary>
        private TrackedGameObject emitter;

        /// <summary>
        /// The goal which must be reached by the ball.
        /// </summary>forceCollider
        private TrackedGameObject goal;

        /// <summary>
        /// The simple collider.
        /// </summary>
        private TrackedGameObject simpleCollider;

        /// <summary>
        /// The force collider.
        /// </summary>
        private TrackedGameObject forceCollider;

        /// <summary>
        /// The split collider.
        /// </summary>
        private TrackedGameObject splitCollider;

        /// <summary>
        /// The spin collider.
        /// </summary>
        private TrackedGameObject spinCollider;

        /// <summary>
        /// The blink collider.
        /// </summary>
        private TrackedGameObject blinkCollider;

        /// <summary>
        /// The ball game object.
        /// </summary>
        private GameObject ballObject;

        /// <summary>
        /// The current game state.
        /// </summary>
        private GameState gameState;

        /// <summary>
        /// The current game round of the player.
        /// </summary>
        private int roundCounter;

        /// <summary>
        /// The adjustent phase timer
        /// </summary>
        private float adjustTimer;

        /// <summary>
        /// The timer GUI text.
        /// </summary>
        private GUIText timerText;

        /// <summary>
        /// The player score.
        /// </summary>
        private int score;

        /// <summary>
        /// The score GUI text.
        /// </summary>
        private GUIText scoreText;

        /// <summary>
        /// The speed of the next ball.
        /// </summary>
        private int nextSpeed;

        /// <summary>
        /// The score GUI text.
        /// </summary>
        private GUIText nextSpeedText;

        /// <summary>
        /// The emitter rotation of the next game.
        /// </summary>
        private Quaternion nextRotation;

        /// <summary>
        /// The message GUI text.
        /// </summary>
        private GUIText messageText;

        /// <summary>
        /// Sets up the scene and the AR system.
        /// </summary>
        void Awake()
        {
            // setup unity webcam
            WebCamDevice[] devices = WebCamTexture.devices;
            WebCamTexture webCamTexture;

            if (devices.Length > 0)
            {
                webCamTexture = new WebCamTexture(320, 240, 15);
                this.webCamSensor = new NyARUnityWebCam(webCamTexture);

                NyARMarkerSystemConfig config = new NyARMarkerSystemConfig(
                    webCamTexture.requestedWidth,
                    webCamTexture.requestedHeight);
                this.markerSystem = new NyARUnityMarkerSystem(config);

                // created trackers
                this.emitter = new TrackedGameObject(this.markerSystem, "./Assets/Data/0.pat");
                this.goal = new TrackedGameObject(this.markerSystem, "./Assets/Data/6.pat");
                this.simpleCollider = new TrackedGameObject(this.markerSystem, "./Assets/Data/1.pat");
                this.forceCollider = new TrackedGameObject(this.markerSystem, "./Assets/Data/2.pat");
                this.splitCollider = new TrackedGameObject(this.markerSystem, "./Assets/Data/3.pat");
                this.spinCollider = new TrackedGameObject(this.markerSystem, "./Assets/Data/4.pat");
                this.blinkCollider = new TrackedGameObject(this.markerSystem, "./Assets/Data/5.pat");

                // setup background
                this.backgroundPanel = GameObject.Find("Plane");
                this.backgroundPanel.renderer.material.mainTexture = webCamTexture;
                this.markerSystem.setARBackgroundTransform(this.backgroundPanel.transform);

                // setup camera projection
                this.markerSystem.setARCameraProjection(this.camera);
            }
            else
            {
                Debug.LogError("No Webcam.");
            }
        }

        /// <summary>
        /// Starts the web cam.
        /// </summary>
        void Start()
        {
            //start sensor
            this.webCamSensor.start();

            // initialze trackers with associated game objects
            this.emitter.Initialize("Emitter");
            this.emitter.FixRotationToMarker = false;
            this.goal.Initialize("Goal");
            this.simpleCollider.Initialize("SimpleCollider");
            this.forceCollider.Initialize( "ForceCollider");
            this.splitCollider.Initialize( "SplitCollider");
            this.spinCollider.Initialize("SpinCollider");
            this.spinCollider.FixRotationToMarker = false;
            this.blinkCollider.Initialize("BlinkCollider");

            // Text UI
            timerText = GameObject.Find("TimerText").GetComponent<GUIText>();
            scoreText = GameObject.Find("ScoreText").GetComponent<GUIText>();
            nextSpeedText = GameObject.Find("NextSpeedText").GetComponent<GUIText>();
            messageText = GameObject.Find("MessageText").GetComponent<GUIText>();

            showMessage("Press ENTER to start");
        }

        /// <summary>
        /// Updates the AR scene.
        /// </summary>
        void Update()
        {
            // update sensor system
            this.webCamSensor.update();
            // update marker system by the web cam sensor
            this.markerSystem.update(this.webCamSensor);

            if (gameState == GameState.Start)
            {
                if (Input.GetKeyDown(GameConfig.ACTION_KEY))
                {
                    // verify emitter is detected
                    if (!emitter.IsDetected)
                    {
                        showMessage("Emitter not detected. Please check your setup...");
                    }
                    // verify goal is detected
                    else if (!goal.IsDetected)
                    {
                        showMessage("Goal not detected. Please check your setup...");
                    }
                    // start the game
                    else
                    {
                        // start game
                        this.StartRound();
                    }
                }

                // track objects before the game has started, so the player
                // can test the camera setting, room lighting and placement of
                // start and goal.
                this.emitter.Update();
                this.goal.Update();
                this.simpleCollider.Update();
                this.forceCollider.Update();
                this.splitCollider.Update();
                this.spinCollider.Update();
                this.blinkCollider.Update();   
            }
            else if (gameState == GameState.Adjusting)
            {
                adjustTimer -= Time.deltaTime;
                adjustTimer = Math.Max(adjustTimer, 0.0f);

                // time UI
                timerText.text = string.Format("{0:00}:{1:00}", (int)adjustTimer, (int)((adjustTimer - (int)adjustTimer) * 60));

                //change arrow direction
                emitter.GameObject.transform.rotation = nextRotation;

                // check for switching to the playing mode
                if (adjustTimer <= 0.0f || Input.GetKeyDown(GameConfig.ACTION_KEY))
                {
                    // verify emitter is detected
                    if (!emitter.IsDetected)
                    {
                        showMessage("Emitter not detected. Retrying...");
                        adjustTimer += 1.0f;
                    }
                    // verify goal is detected
                    else if (!goal.IsDetected)
                    {
                        showMessage("Goal not detected. Retrying...");
                        adjustTimer += 1.0f;
                    }
                    // start the game
                    else
                    {
                        gameState = GameState.Playing;

                        // time UI
                        timerText.text = string.Format("PLAYING ROUND {0}/{1}...", roundCounter, GameConfig.GAME_ROUNDS);

                        // create ball
                        this.ballObject = Utility.CreateBall("Ball",
                                    this.emitter.GameObject.transform.position,
                                    this.emitter.GameObject.transform.rotation);
                        Utility.TranslateToXYLayer(ballObject, GameConfig.POSITION_Z);

                        // give the ball the appropriate impuls
                        ballObject.rigidbody.AddForce(-this.emitter.GameObject.transform.up.normalized * nextSpeed,
                            ForceMode.Impulse);

                        adjustTimer = 0.0f;
                    }
                }
                else
                {
                    this.emitter.Update();
                    this.goal.Update();
                    this.simpleCollider.Update();
                    this.forceCollider.Update();
                    this.splitCollider.Update();
                    this.spinCollider.Update();
                    this.blinkCollider.Update();
                }
            }
            else if (gameState == GameState.Playing)
            {
                // do noting special here. Just wait until the ball notify this
                // main script that the round is over.
            }
            else if (gameState == GameState.GameOver)
            {
                if (Input.GetKeyDown(GameConfig.ACTION_KEY))
                {
                    gameState = GameState.Start;
                    score = 0;
                    roundCounter = 0;
                    showMessage("Press ENTER to start");
                }
                else
                {
                    this.scoreText.text = "";
                    this.timerText.text = "";
                    this.nextSpeedText.text = "";

                    if (score > 0)
                        showMessage("You have a score of {0}. Congratulation!", score);
                    else
                        showMessage("You have a score of {0}. Try again!", score);
                }
            }

            update3dTexts();

            if (gameState != GameState.Start && gameState != GameState.GameOver)
                this.scoreText.text = string.Format("Score: {0:0000}", score);
        }

        /// <summary>
        /// Callback for the ball to notify the game that the round is over.
        /// </summary>
        public void EndRound()
        {
            if (roundCounter < GameConfig.GAME_ROUNDS)
                StartRound();
            else
            {
                gameState = GameState.GameOver;
            }
        }

        /// <summary>
        /// Changes the player score.
        /// </summary>
        /// <param name="value">The value to add.</param>
        public void ChangeScore(int value)
        {
            this.score += value;
        }

        /// <summary>
        /// Starts a new game.
        /// </summary>
        private void StartRound()
        {
            gameState = GameState.Adjusting;
            roundCounter++;

            adjustTimer = GameConfig.MARKER_ADJUST_TIME;

            nextSpeed = UnityEngine.Random.Range(GameConfig.MIN_BALL_IMPULS, GameConfig.MAX_BALL_IMPULS);
            this.nextSpeedText.text = string.Format("Next ball speed: {0}%", (int)(100.0 * nextSpeed / GameConfig.MAX_BALL_IMPULS));

            nextRotation = Utility.RotateOrthogonalToXYLayer(UnityEngine.Random.rotation);
            emitter.GameObject.transform.rotation = nextRotation;

            this.scoreText.text = "";
            this.timerText.text = "";

            hideMessage();
        }

        /// <summary>
        /// Updates the 3D Text positions.
        /// </summary>
        private void update3dTexts()
        {
            Vector3 offset = Vector3.up * 60;
            GameObject.Find("Emitter_text").transform.localPosition = emitter.GameObject.transform.position + offset;
            GameObject.Find("Goal_text").transform.localPosition = goal.GameObject.transform.position + offset;
            GameObject.Find("Simple_text").transform.localPosition = simpleCollider.GameObject.transform.position + offset;
            GameObject.Find("Force_text").transform.localPosition = forceCollider.GameObject.transform.position + offset;
            GameObject.Find("Split_text").transform.localPosition = splitCollider.GameObject.transform.position + offset;
            GameObject.Find("Spin_text").transform.localPosition = spinCollider.GameObject.transform.position + offset;
            GameObject.Find("Blink_text").transform.localPosition = blinkCollider.GameObject.transform.position + offset;
        }

        /// <summary>
        /// Shows a message to the user.
        /// </summary>
        /// <param name="format">The format string.</param>
        /// <param name="args">The arguments</param>
        private void showMessage(string format, params object[] args)
        {
            messageText.text = string.Format(format, args);
        }

        /// <summary>
        /// Hides the user message.
        /// </summary>
        private void hideMessage()
        {
            showMessage("");
        }
    }
}
